﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.ComponentModel;
using System.Reflection;
using ArgusLib.Drawing;
using WinForms = System.Windows.Forms;

namespace ArgusLib.Controls
{
	public class ListBox : System.Windows.Forms.ListBox
	{
		[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
		private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam);
		private static FieldInfo fieldIsDirty;
		private static MethodInfo fireSelectedIndexChanged;

		static ListBox()
		{
			fieldIsDirty = typeof(ListBox.SelectedObjectCollection).GetField("stateDirty", BindingFlags.NonPublic | BindingFlags.Instance);
			fireSelectedIndexChanged = typeof(ListBox).GetMethod("OnSelectedIndexChanged", BindingFlags.NonPublic | BindingFlags.Instance);
		}

		public ListBox()
			: base()
		{
			base.BackColor = Color.Black;
			base.ForeColor = Color.White;
			this.AllowedKeyShortcuts = KeyShortcuts.None;
			this.Appearance = Appearances.ImageAndText;
			base.DrawMode = WinForms.DrawMode.OwnerDrawFixed;
			this.Images = new ImageCollection();
			this.Images.ImageSize = new Size(0, this.ItemHeight);
			this.Images.ImageSizeChanged += Images_ImageSizeChanged;
			this.Images.ItemAdded += Images_ItemAdded;
		}

		public KeyShortcuts AllowedKeyShortcuts { get; set; }
		public ImageCollection Images { get; private set; }
		public Appearances Appearance { get; set; }

		public event KeyShortcutSelectedRemovedEventHandler KeyShortcutSelectedRemoved;

		void Images_ImageSizeChanged(object sender, EventArgs e)
		{
			if (this.Images.ImageSize.Height > 255)
				throw new ArgumentOutOfRangeException("Images.ImageSize.Height", "Height must be smaller than 256");
			if (this.Images.ImageSize.Height != this.ItemHeight)
				this.ItemHeight = this.Images.ImageSize.Height;
		}

		void Images_ItemAdded(object sender, ObjectCollection<Image>.ItemEventArgs e)
		{
			int index = this.Items.IndexOf(e.Key);
			if (index < this.TopIndex)
				return;

			int count = this.ClientSize.Height / this.ItemHeight + 1;
			if (index > this.TopIndex + count)
				return;
			this.Refresh();
		}

		public override int ItemHeight
		{
			get { return base.ItemHeight; }
			set
			{
				base.ItemHeight = value;
				this.Images.ImageSize = new Size(this.Images.ImageSize.Width, value);
			}
		}

		public int ImageWidth
		{
			get { return this.Images.ImageSize.Width; }
			set { this.Images.ImageSize = new Size(value, this.Images.ImageSize.Height); }
		}

		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
		public new WinForms.DrawMode DrawMode
		{
			get { return base.DrawMode; }
			set { }
		}

		public void MoveSelectedUp()
		{
			if (this.SelectedIndices.Count < 1)
				return;

			object[] items = new object[this.Items.Count];
			List<object> unselected = new List<object>();
			List<int> isSelected = new List<int>();

			if (this.GetSelected(0) == false)
			{
				unselected.Add(this.Items[0]);
			}
			else
			{
				items[0] = this.Items[0];
				isSelected.Add(0);
			}

			for (int i = 1; i < this.Items.Count; i++)
			{
				if (this.GetSelected(i) == false)
				{
					unselected.Add(this.Items[i]);
					continue;
				}

				if (items[i - 1] == null)
				{
					items[i - 1] = this.Items[i];
					isSelected.Add(i - 1);
				}
				else
				{
					items[i] = this.Items[i];
					isSelected.Add(i);
				}
			}

			int index = 0;
			for (int i = 0; i < items.Length; i++)
			{
				if (items[i] != null)
					continue;
				items[i] = unselected[index];
				index++;
			}
			this.Items.Clear();
			this.Items.AddRange(items);
			foreach (int s in isSelected)
			{
				this.SetSelected(s, true);
			}
		}

		public void MoveSelectedDown()
		{
			if (this.SelectedIndices.Count < 1)
				return;

			object[] items = new object[this.Items.Count];
			List<object> unselected = new List<object>();
			List<int> isSelected = new List<int>();

			if (this.GetSelected(this.Items.Count-1) == false)
			{
				unselected.Add(this.Items[this.Items.Count - 1]);
			}
			else
			{
				items[this.Items.Count - 1] = this.Items[this.Items.Count - 1];
				isSelected.Add(this.Items.Count - 1);
			}

			for (int i = this.Items.Count - 2; i > -1; i--)
			{
				if (this.GetSelected(i) == false)
				{
					unselected.Add(this.Items[i]);
					continue;
				}

				if (items[i + 1] == null)
				{
					items[i + 1] = this.Items[i];
					isSelected.Add(i + 1);
				}
				else
				{
					items[i] = this.Items[i];
					isSelected.Add(i);
				}
			}

			int index = 0;
			for (int i = items.Length-1; i > -1; i--)
			{
				if (items[i] != null)
					continue;
				items[i] = unselected[index];
				index++;
			}
			this.Items.Clear();
			this.Items.AddRange(items);
			foreach (int s in isSelected)
			{
				this.SetSelected(s, true);
			}
		}

		/// <summary>
		/// Removes all selected items from the <see cref="ListBox"/>.
		/// </summary>
		/// <returns>An array containing the removed items.</returns>
		public object[] RemoveSelected()
		{
			object[] RetVal = new object[this.SelectedItems.Count];
			this.SelectedItems.CopyTo(RetVal, 0);
			List<object> items = new List<object>(this.Items.Count - this.SelectedItems.Count);
			for (int i = 0; i < this.Items.Count; i++)
			{
				if (this.GetSelected(i) == false)
					items.Add(this.Items[i]);
			}
			this.BeginUpdate();
			this.Items.Clear();
			this.Items.AddRange(items.ToArray());
			this.EndUpdate();
			return RetVal;
		}

		public bool SelectAll()
		{
			if (this.SelectionMode != WinForms.SelectionMode.MultiSimple && this.SelectionMode != WinForms.SelectionMode.MultiExtended)
				return false;
			SendMessage(this.Handle, 0x185, new IntPtr(1), new IntPtr(-1));
			fieldIsDirty.SetValue(this.SelectedItems, true);
			fireSelectedIndexChanged.Invoke(this, new object[] { new EventArgs() });
			return true;
		}

		public void DeselectAll()
		{
			SendMessage(this.Handle, 0x185, new IntPtr(0), new IntPtr(-1));
			fieldIsDirty.SetValue(this, true);
			fireSelectedIndexChanged.Invoke(this, new object[] { new EventArgs() });
		}

		protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
		{
			if (this.AllowedKeyShortcuts != KeyShortcuts.None)
			{
				if (this.AllowedKeyShortcuts.HasFlag(KeyShortcuts.SelectAll) == true)
				{
					if (ModifierKeys == WinForms.Keys.Control && e.KeyCode == WinForms.Keys.A)
					{
						this.SelectAll();
					}
				}
				if (this.AllowedKeyShortcuts.HasFlag(KeyShortcuts.DeleteSelected) == true)
				{
					if (e.KeyCode == WinForms.Keys.Delete)
					{
						object[] removedItems = this.RemoveSelected();
						this.OnKeyShortcutSelectedRemoved(new KeyShortcutSelectedRemovedEventArgs(removedItems));
					}
				}
				if (this.AllowedKeyShortcuts.HasFlag(KeyShortcuts.DeselectAll) == true)
				{
					if (e.KeyCode == WinForms.Keys.Escape)
					{
						this.DeselectAll();
					}
				}
			}
			base.OnKeyDown(e);
		}

		protected virtual void OnKeyShortcutSelectedRemoved(KeyShortcutSelectedRemovedEventArgs e)
		{
			if (this.KeyShortcutSelectedRemoved != null)
				this.KeyShortcutSelectedRemoved(this, e);
			this.Images.RemoveKeyRange(e.RemovedItems);
		}

		protected override void OnDrawItem(WinForms.DrawItemEventArgs e)
		{
			e.DrawBackground();
			if (e.Index > -1 && this.Items.Count > 0)
			{
				if (this.Appearance == Appearances.ImageAndText)
				{
					e.Graphics.DrawString(
							this.Items[e.Index].ToString(),
							e.Font,
							new SolidBrush(e.ForeColor),
							e.Bounds.Left + this.Images.MaxWidth,
							e.Bounds.Top + (e.Bounds.Height - e.Font.Height) / 2);
				}
				else if (this.Appearance == Appearances.Text)
				{
					e.Graphics.DrawString(
							this.Items[e.Index].ToString(),
							e.Font,
							new SolidBrush(e.ForeColor),
							e.Bounds.Left,
							e.Bounds.Top + (e.Bounds.Height - e.Font.Height) / 2);
				}

				if (this.Appearance == Appearances.ImageAndText || this.Appearance == Appearances.Image)
				{
					Image image;
					if (this.Images.TryGetValue(this.Items[e.Index], out image) == true && image != null)
					{
						e.Graphics.DrawImageUnscaled(
							image,
							e.Bounds.Left + (this.Images.MaxWidth - image.Width) / 2,
							e.Bounds.Top + (this.Images.MaxHeight - image.Height) / 2);
					}
				}
			}
			e.DrawFocusRectangle();
			base.OnDrawItem(e);
		}

		[Flags]
		public enum KeyShortcuts
		{
			None = 0,
			SelectAll		= 1 << 0,
			DeleteSelected	= 1 << 1,
			DeselectAll		= 1 << 2,

			All = SelectAll|DeleteSelected|DeselectAll
		}

		public enum Appearances
		{
			ImageAndText,
			Text,
			Image
		}

		public class KeyShortcutSelectedRemovedEventArgs : EventArgs
		{
			public object[] RemovedItems { get; private set; }

			public KeyShortcutSelectedRemovedEventArgs(object[] removedItems)
				: base()
			{
				this.RemovedItems = removedItems;
			}
		}

		public delegate void KeyShortcutSelectedRemovedEventHandler(object sender, KeyShortcutSelectedRemovedEventArgs e);
	}
}
